#include <EEPROM.h>

#include "SPI.h"
#include "FastLED.h"
#include "rastermap.h"
#include "program.h"
#include "CapacitiveSensor.h"

#define NUM_LEDS 78

#define W 6
#define H 16


int strip_map[W * H] = {
0,   29,  30,  55,  56,  70,
1,   28,  31,  54,  57,  71,
2,   27,  32,  53,  58,  72,
3,   26,  33,  52,  59,  73,
4,   25,  34,  51,  60,  74,
5,   24,  35,  50,  61,  75,
6,   23,  36,  49,  62,  76,
7,   22,  37,  48,  63,  77,
8,   21,  38,  47,  64,  -1,
9,   20,  39,  46,  65,  -1,
10,  19,  -1,  45,  66,  -1,
11,  18,  -1,  44,  67,  -1,
12,  17,  -1,  43,    68,  -1,
13,  16,  -1,  42,  69,  -1,
-1,  15,  -1,  41,  -1,  -1,
-1,  14,  -1,  40,  -1,  -1,
};

const int LPD_DPIN = 3;
const int LPD_CPIN = 2;

const unsigned long PROGRAM_SECONDS = 60;
const unsigned long PROGRAM_TIME = PROGRAM_SECONDS * 1e3;

unsigned long last_program_change = 0;
unsigned long frames = 0;
int dim = 0;

CRGB color_array[NUM_LEDS];

BitMap lights(color_array, NUM_LEDS, 1);
RasterMapper displayMapper(strip_map, &lights, W, H);
Raster& display = displayMapper;

Life<W, H> life(2, 64);  // Fade speed.
                         // sleep.
Sprinker sprinker(1, -30, 3);  // fade_speed (higher is faster).
                               // min_drops - greater negative increases chance of no drops.
                               // max_drops
Rain rain(1, 5, 8, 16);   // fade_speed (higher is faster).
                          // drop_count.
                          // min_grow_speed (higher is faster).
                          // max_grow_speed (higher is faster).
CylonBar cylon_bar(3, 32);  // fade_speed (higher is faster).
                            // grow_speed (higher is faster).

int UpArrowsFn(int x, int y) {
  int width = display.width();
  return y + (abs(x - (width >> 2)) * 32);
}

FunctionSpinner up_arrows;

ProgramSelector program_selector(
  Programs(
    &cylon_bar,
    &rain,
    &sprinker,
    &life,
    NULL));

DoubleBuffer<W, H> main_program(program_selector);

CapacitiveSensor cs_circle = CapacitiveSensor(10, 9); 
CapacitiveSensor cs_plus = CapacitiveSensor(10, 12);

class Button {
  unsigned long reset_delay_;
  int threshold_;
  int pin_;
  unsigned long last_press_;
  boolean pressed_;
  CapacitiveSensor* sensor_;
 public:
  Button(CapacitiveSensor& sensor, long reset_delay, int threshold)
    : sensor_(&sensor),
      reset_delay_(reset_delay),
      threshold_(threshold),
      last_press_(0),
      pressed_(false) {
    }
    
  void Init() {
    sensor_->set_CS_AutocaL_Millis(0xFFFFFFFF);
  }

  boolean triggered() {
    long value = sensor_->capacitiveSensor(30);
    boolean is_now_pressed = value >= threshold_;
    unsigned long now = millis();
    boolean allowed_state_change = (now - last_press_) >= reset_delay_;
    if (allowed_state_change && pressed_ != is_now_pressed) {
      pressed_ = is_now_pressed;
      last_press_ = now;
      return pressed_;
    }
    return false;
  }


  boolean released() {
    long value = sensor_->capacitiveSensor(30);
    boolean is_now_pressed = value >= threshold_;
    unsigned long now = millis();
    boolean allowed_state_change = (now - last_press_) >= reset_delay_;
    if (allowed_state_change && pressed_ != is_now_pressed) {
      pressed_ = is_now_pressed;
      last_press_ = now;
      return pressed_;
    }
    return false;
  }
};

Button circle_button(cs_circle, 100, 100);
Button plus_button(cs_plus, 100, 100);

void setup() {
  Serial.begin(9600);
  Serial.println("Begin initialization");
  randomSeed(analogRead(0));
  FastLED.addLeds<LPD8806, LPD_DPIN, LPD_CPIN, RGB>((CRGB*)color_array, NUM_LEDS);

  last_program_change = millis();

  main_program.Reset(display);

  circle_button.Init();
  plus_button.Init();
  up_arrows.SetFunctor(UpArrowsFn);

  Serial.println("End initialization");
}


void BoomBoom(Raster& raster, int booms=3) {
  for (int i = 0; i < booms; ++i) {
    for (int c = MAX_COLOR; c >= 0; c -= 3) {
      raster.Clear(CRGB(c, c, c));
      FastLED.show();
    }
    delay(100);  // Use this to speed up or slow down.
  }
}

void loop() {
  if (main_program.DoLoop(0)) {
    if (dim) {
      lights.Dim(dim);
    }
    FastLED.show();
  }
  
  frames++;
  unsigned long now = millis();
  unsigned long time_since_change = now - last_program_change;
  if (time_since_change > PROGRAM_TIME) {
    // Change program!
    int next_program = (program_selector.current_program() + 1) % program_selector.program_count();
    Serial.print("Changing to program ");
    Serial.println(next_program, DEC);
    program_selector.SetProgram(next_program);

    last_program_change = now;
    int fps = frames / PROGRAM_SECONDS;
    Serial.print("FPS: ");
    Serial.println(fps, DEC);
    frames = 0;
  }

  if (circle_button.triggered()) {
    BoomBoom(lights);
  }

  if (plus_button.triggered()) {
    dim = (dim + 2) % 6;
  }
  delay(5);  // Apparenly was executing too fast without this.
}





